/* 
 数据类型检测专题汇总
   @1 typeof [value]
    返回值：首先是一个字符串、其次字符串中包含对应的数据类型
    原理：按照计算机底层存储的二进制值进行检测「速度相对快」
    特征：
      + typeof null -> "object"
      + typeof无法细分对象，但是可以识别函数对象
      + typeof检测一个未被声明的变量，不会报错，结果是“undefined”
    实战运用：
      + 检测除null之外的原始值类型 
      + 检测是否是函数
      + 笼统的检测是否为对象
      + 检测是否兼容
      + ...

  @2 [value] instanceof [constructor]
    检测一个值(实例)是否为[constructor]类的实例「临时拉其做检测数据类型的办法：通过验证这个值是否为某一个类的实例，来判断是否属于这个类型」
    原理：
      + 首先检测[constructor]是否具备Symbol.hasInstance属性方法
        如果具备：则执行这个方法 [constructor][Symbol.hasInstance]([value])
          在兼容ES6语法的浏览器中，Function.prototype上具备Symbol.hasInstance方法，所以相当于所有的函数都具备这个方法！
        如果不具备：则按照下述操作进行检测
      + 按照原型链进行查找检测的「如果当前类的原型对象，出现在检测值的原型链上，则结果是true」
    特征：
      + 检测对象是否为Object实例，结果都是true，所以无法验证是否为标准普通对象
      + instanceof无法处理原始值类型的检测的「检测结果都是false，原因：原始值不具备__proto__」
      + 因为原型链指向是可以被修改的，而instanceof完全依赖原型链，所以检测出来的结果“仅供参考”
    实战：
      + 检测是否为某个类的实例
      + 偶尔“不需要多么严谨”的数据类型检测，可以用它来处理一下，例如：细分对象

  @3 [value].constructor===[ctor]
    检测一个值的构造函数是否是XXX，从而判断他的数据类型！
    if(arr.constructor===Array){ 
        //arr是一个数组 
    }
    特点：
      + 因为constructor很容易被修改，所以检测的结果“仅供参考”
      + 可以处理原始值「默认“装箱”」
      + 可以区分是否为“标准普通对象”

  @4 Object.prototype.toString.call([value])
    除Object之外，其余数据类型内置类(例如：Number/String/Function/Array...)的原型对象上，也存在toString方法，只不过都是用来转换为字符串的！！只有Object.prototype.toString是用来检测数据类型的！
    返回值：“[object ?]”
      + 检测原始值，“?”这部分是自己所属的构造函数「null->Null undefined->Undefined」
      + 检测的是对象，可以很精准的区分是哪一种对象（弥补了typeof无法细分对象的弊端）
        “?”这部分是啥值：
        + 首先查看[value]值是否具备Symbol.toStringTag这个属性，如果有，属性值是啥，最后“?”这部分就是啥；如果没有，一般是获取自己所属构造函数的名字！！
    实战：
      + 检测对象(而且是想细分对象)，则基于toString这个办法
 */


/* 
 toType:通用检测数据类型的办法「内部会按照最合理的方式，检测不同值的类型」
   @params
     obj [any]:要检测的值
   @return
     [string] “数据类型(小写)”
 */
/* const toString = Object.prototype.toString;
const toType = function toType(obj) {
    if (obj === null) return "null"; //obj是null
    let type = typeof obj,
        reg1 = /^(object|function)$/,
        reg2 = /^\[object (\w+)\]$/;
    if (!reg1.test(type)) return type; //obj是原始值
    // obj是一个对象
    type = toString.call(obj); //"[object ?]"
    return reg2.exec(type)[1].toLowerCase();
}; */

/* class Fn {}
let f = new Fn;
console.log(toString.call(f)); //"[object Object]" 默认情况下，检测自定义类实例的类型，“?”这部分的值，不是自己所属构造函数，一般是Object */
/* // 需求："[object Fn]"
class Fn { }
Fn.prototype[Symbol.toStringTag] = "Fn";
let f = new Fn;
console.log(toString.call(f)); //"[object Fn]" */


/* let toString = Object.prototype.toString;
let arr = [10, 20];
let obj = { name: 'xxx' }; */

/* arr[Symbol.toStringTag] = "EmptyArray";
console.log(toString.call(arr)); //"[object EmptyArray]" */

/* console.log(arr.toString()); //Array.prototype.toString => "10,20"
console.log(toString.call(arr)); //Object.prototype.toString => "[object Array]"
console.log(obj.toString()); //Object.prototype.toString => "[object Object]" */
/*
 alert(obj); //"[object Object]"
 alert会把所有的内容转换为字符串再输出 => String(obj)
   + obj[Symbol.toPrimitive] -> undefined
   + obj.valueOf() -> 获取的不是原始值
   + obj.toString() -> 调用Object.prototype.toString，是用来检测数据类型的
 */







//============================================
/* let arr = [10, 20];
console.log(arr instanceof Array);//true
console.log(arr instanceof Object);//true
console.log(arr.constructor === Array);//true
console.log(arr.constructor === Object);//false */

/* let num = 10;
console.log(num.constructor === Number); //true */

//============================================
/* 
 instance_of：对内置instanceof的重写
   @params
     value:需要检测的值(实例)
     ctor:需要检测的构造函数(类)
   @return
     返回true/false
 */
/* const instance_of = function instance_of(value, ctor) {
    // value->arr  ctor->RegExp/Array/Object
    let proto = Object.getPrototypeOf(value);
    while (proto) {
        // 按照原型链一层层进行查找，如果某一层的原型对象和被检测的“构造函数的原型对象一致”，则说明value是ctor的一个实例对象，直接返回true即可！！
        if (proto === ctor.prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
    // 循环已经结束，没有发现任何符合条件的，则说明value的原型链上，没有出现过ctor.prototype，value也就不是ctor的实例！
    return false;
}; */

/* const instance_of = function instance_of(value, ctor) {
    // 格式校验
    if (typeof ctor !== "function") throw new TypeError("Right-hand side of 'instanceof' is not callable");
    if (value == null || !/^(object|function)$/.test(typeof value)) return false;
    if (!ctor.hasOwnProperty('prototype')) throw new TypeError("Function has non-object prototype 'undefined' in instanceof check");
    // 兼容ES6的浏览器
    if (typeof Symbol !== "undefined") {
        let hasInstance = ctor[Symbol.hasInstance];
        if (hasInstance) return hasInstance.call(ctor, value);
    }
    // 按照原型链检测
    let proto = Object.getPrototypeOf(value);
    while (proto) {
        if (proto === ctor.prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
    return false;
};
let arr = [10, 20];
console.log(instance_of(arr, RegExp));
console.log(instance_of(arr, Array));
console.log(instance_of(arr, Object));
// console.log(arr instanceof RegExp); //false
// console.log(arr instanceof Array); //true
// console.log(arr instanceof Object); //true */



/* let obj = {};
obj.__proto__ = Array.prototype;
console.log(obj instanceof Array); //true */

/* 
// 可以基于ES6的语法对类的Symbol.hasInstance进行重写：内置类基本是没有办法重写的，所以一般都是对自定义类的这个属性进行重写！！
class Fn {
    constructor(name) {
        if (name) {
            this.name = name;
        }
    }
    /!* 静态私有属性方法 *!/
    static [Symbol.hasInstance](obj) {
        return obj.hasOwnProperty('name');
    }
}
let f1 = new Fn;
let f2 = new Fn('珠峰培训');
console.log(f1 instanceof Fn); //Fn[Symbol.hasInstance](f1)
console.log(f2 instanceof Fn); 
*/

/* 
let arr = [10, 20];
// 基于ES5的方法，重写函数的Symbol.hasInstance属性，是不生效的
Array[Symbol.hasInstance] = function (value) {
    return false;
};
console.log(arr instanceof Array); //true 
*/

/* 
 console.log(arr instanceof Array); 
 首先查找Array是否具备Symbol.hasInstance这个方法「非IE浏览器都具备」
   Array[Symbol.hasInstance](arr)
 */

/* console.log(arr instanceof RegExp); //false
console.log(arr instanceof Array); //true
console.log(arr instanceof Object); //true */

